#pragma once

#include <cstdint>
#include <vector>
#include <list>
#include <unordered_set>
#include "../util/allocator.hh"

#define CLASS_FIELD(name, type) \
    type name##_;

#define GETTER(name, type, field) \
    inline type get##name() const { \
        return field##_; \
    }

#define GETTER_SETTER(name, type, field) \
    inline void set##name(type v) { \
        field##_ = v; \
    } \
    inline type get##name() const { \
        return field##_; \
    }

namespace kratos { namespace corelib {

class AoiEntity;
class AoiScene;

using EntityVector = std::vector<AoiEntity*>;
using EntityList = std::list<AoiEntity*>;

// AOI(Area of interest) Entity
// The AOI of entity is a square
class AoiEntity {
public:
    enum {
        TYPE_NORMAL = 1, // Entity
        TYPE_ANCHOR, // Anchor
    };

public:
    // ctor
    // @param scene AOI scene
    // @param id entity ID
    // @param x x axis
    // @param y y axis
    // @param initial AOI
    // @param type TYPE_NORMAL or TYPE_ANCHOR
    AoiEntity(AoiScene* scene, uint64_t id, std::int32_t x, std::int32_t y,
        std::int32_t aoi, int32_t type)
        : scene_(scene), id_(id), prevx_(nullptr), nextx_(nullptr), prevy_(nullptr),
          nexty_(nullptr), x_(x), y_(y), aoi_(aoi), type_(type) {
    }
    // dtor
    ~AoiEntity() {
        scene_ = nullptr;
        id_ = 0;
        prevx_ = nullptr;
        nextx_ = nullptr;
        prevy_ = nullptr;
        nexty_ = nullptr;
        x_ = 0;
        y_ = 0;
        aoi_ = 0;
        type_ = 0;
    }
    // Move in AOI scene
    // @param x new x
    // @param y new y
    void move(std::int32_t x, std::int32_t y);
    // Setup new AOI
    // @param aoi new AOI
    void setAoi(std::int32_t aoi);
    // Is in my AOI?
    // @param other another entity
    // @retval true
    // @retval false
    inline bool inAoi(AoiEntity* other) {
        auto dx = abs(other->x_ - x_);
        auto dy = abs(other->y_ - y_);
        return (dx < aoi_ && dy < aoi_);
    }
    // Is in a circle with AOI radius?
    // @param other another entity
    // @retval true
    // @retval false
    inline bool inCircle(AoiEntity* other) {
        auto dx = other->x_ - x_;
        auto dy = other->y_ - y_;
        return (aoi_ * aoi_ >= dx * dx + dy * dy);
    }
    // Should skip for AOI test or not
    // @retval true
    // @retval false
    inline bool isSkip() {
        return (type_ == AoiEntity::TYPE_ANCHOR);
    }

    GETTER(Aoi,      std::int32_t,   aoi)
    GETTER(ID,       std::uint64_t,  id)
    GETTER(Scene,    AoiScene*,      scene)
    GETTER_SETTER(X, std::int32_t,   x)
    GETTER_SETTER(Y, std::int32_t,   y)

private:
    friend class AoiScene;
    GETTER_SETTER(Type, int32_t, type)

private:
    CLASS_FIELD(scene, AoiScene*) // AOI scene
    CLASS_FIELD(id,    uint64_t) // entity ID
    CLASS_FIELD(prevx, AoiEntity*) // Previous entity in x axis
    CLASS_FIELD(nextx, AoiEntity*) // Next entity in x axis
    CLASS_FIELD(prevy, AoiEntity*) // Previous entity in y axis
    CLASS_FIELD(nexty, AoiEntity*) // Next entity in y axis
    CLASS_FIELD(x,     std::int32_t) // x axis
    CLASS_FIELD(y,     std::int32_t) // y axis
    CLASS_FIELD(aoi,   std::int32_t) // AOI
    CLASS_FIELD(type,  int32_t) // entity type
};
// AOI scene
class AoiScene {
    using EntityMap = UNORDERED_MAP(std::uint64_t, AoiEntity*, AoiScene_entityMap_);
    EntityMap entityMap_; // Entity map

public:
    // ctor
    // @param startx starter of x axis
    // @param starty starter of y axis
    // @param endx end of x axis
    // @param endy end of y axis
    // @param splitRange The split for anchor
    AoiScene(std::int32_t startx, std::int32_t starty, std::int32_t endx,
        std::int32_t endy, std::int32_t splitRange = 50);
    // dtor
    ~AoiScene();
    // Enter the scene
    // @param id entity ID
    // @param x x axis
    // @param y y axis
    // @param aoi AOI of entity
    // @retval entity address
    // @retval nullptr fail
    AoiEntity* enter(uint64_t id, std::int32_t x, std::int32_t y, std::int32_t aoi);
    // Leave the scene
    // @param entity entity address
    void leave(AoiEntity* entity);
    // Leave the scene
    // @param id entity ID
    void leave(uint64_t id);
    // Move entity to new position
    // @param entity entity address
    // @param x new x axis
    // @param y new y axis
    void move(AoiEntity* entity, std::int32_t x, std::int32_t y);
    // Move entity to new position
    // @param id entity ID
    // @param x new x axis
    // @param y new y axis
    void move(uint64_t id, std::int32_t x, std::int32_t y);
    // Overlap test for circle
    // @param x center of x axis
    // @param y center of y axis
    // @param radius the radius of circle
    // @param overlappedResult The test result
    // @param except the one need to remove from overlappedResult
    void overlapCircle(std::int32_t x, std::int32_t y, std::int32_t radius, EntityVector& overlappedResult,
        std::uint64_t except = 0);
    // Overlap test for square
    // @param x left of square
    // @param y top of square
    // @param length the length of edge
    // @param overlappedResult The test result
    // @param except the one need to remove from overlappedResult
    void overlapSquare(std::int32_t x, std::int32_t y, std::int32_t length, EntityVector& overlappedResult,
        std::uint64_t except = 0);
    // Search entity by ID
    // @param id entity ID
    // @retval entity address
    // @retval nullptr not found
    AoiEntity* getEntity(std::uint64_t id);
    // Returns amount of entity
    // @return the amount of entity
    std::size_t getSize();

private:
    friend class AoiEntity;
    void insertResult(AoiEntity* entity, EntityVector & overlappedResult, std::uint64_t except);
    AoiEntity* getNearestAnchorX(std::int32_t x, std::int32_t y);
    AoiEntity* getNearestAnchorY(std::int32_t x, std::int32_t y);
    void leave(EntityMap::iterator it);
    void addNodeX(AoiEntity* head, AoiEntity* entity);
    void addNodeY(AoiEntity* head, AoiEntity* entity);
    void delNodeX(AoiEntity* entity);
    void delNodeY(AoiEntity* entity);

private:
    CLASS_FIELD(headX,      AoiEntity*) // The head entity of x axis
    CLASS_FIELD(headY,      AoiEntity*) // The head entity of y axis
    CLASS_FIELD(startx,     std::int32_t) // The start of x axis
    CLASS_FIELD(starty,     std::int32_t) // The start of y axis
    CLASS_FIELD(endx,       std::int32_t) // The end of x axis
    CLASS_FIELD(endy,       std::int32_t) // The end of y axis
    CLASS_FIELD(splitX,     EntityVector) // The anchor vector of of x axis
    CLASS_FIELD(splitY,     EntityVector) // The anchor vector of y axis
    CLASS_FIELD(splitRange, std::int32_t) // The split range of anchor
};

}
}
